import java.util.ArrayDeque;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;                      //not imported by default!
import java.util.HashMap;

public class Game 
{
    private Parser parser;
    private HashMap<String, Room> rooms;
    private ArrayDeque<Room> oldRooms;
    private Player player1;
    private int commandCounter;
    private boolean finished;
    private String lastMessage;

    /**
     * Create the game and initialise its internal map.
     */
    public Game() 
    {
        parser = new Parser();
        oldRooms = new ArrayDeque<Room>();
        rooms = new HashMap<String, Room>();
        
        commandCounter = 0;
        finished = false;
        lastMessage = "Thank you for playing.  Good bye.";
        
        Room startRoom = prepareRooms();
        prepareCharacters(startRoom);
        prepareItems();
        prepareMonsters();
        
    }
    
    /**
     * Prepare all rooms by linking them together, locking exits and inserting items.
     * @return the start room
     */
    private Room prepareRooms()
    {
        // create the rooms and monsters      
        Room caveEntrance, caveRoomEast, caveRoomWest, caveRoomNorth, outsideOtherDimension, ruinsOtherDimension, oldShedOtherDimension, tunnelBeginning, tunnelEnding, basement, churchAltarRoom, churchEastRoom, churchWestRoom, towerLevel1, towerLevel2;
        TeleportRoom teleporterRoom;
        TrapRoom churchMainHall;
        
        caveEntrance = new Room("in the middle of the cave, it looks like there is some sort of teleporter in front of me.");
        caveRoomEast = new Room("in the east room of the cave.");
        caveRoomWest = new Room("in the west room of the cave.");
        caveRoomNorth = new Room("in the north room of the cave.");
        outsideOtherDimension = new Room("outside in a alternate dimention, I can see an old and creepy church in front of me.");
        ruinsOtherDimension = new Room("inside ancient ruins, I hope not to get crushed by falling bolders.");
        oldShedOtherDimension = new Room("inside an old shed, Eaaak! Is that blood ... I hope not.");
        tunnelBeginning = new Room("inside a dark underground tunnel, hhhmmm... seems like I am right under the old ruins...");
        tunnelEnding = new Room("inside a dark underground tunnel, hhhmmm... seems like I am right under the church...");
        churchAltarRoom = new Room("at the altar located in the back of the church, there's a lader over there!");
        churchEastRoom = new Room("inside the right room of the old church.");
        churchWestRoom = new Room("inside the left room of the old church.");
        basement = new Room("inside a dark room....... *feels something on his arm*...  shit, spider !!!");
        towerLevel1 = new Room("at the first floor inside the tower. it looks like there are some lit candles... and another ladder!");
        towerLevel2 = new Room("at the second floor inside the tower, wich happen to be the top level, but .... Chains ! Chains every where ! And treasures !");
        teleporterRoom = new TeleportRoom("in a random room, with an unkown machine, as you step into the machine, you hear a strange buzzing noise...");
        churchMainHall = new TrapRoom("inside the main hall of an old church.", basement);
        
        // initialise room exits and locks
        caveEntrance.setExit("east", caveRoomEast);
        caveEntrance.setExit("west", caveRoomWest);
        caveEntrance.setExit("north", caveRoomNorth);
        caveEntrance.setExit("teleporter", outsideOtherDimension);
        caveEntrance.lockExit("north", "rustykey");
        caveEntrance.lockExit("teleporter", "shinystone");
        
        caveRoomEast.setExit("west", caveEntrance);
        
        caveRoomWest.setExit("east", caveEntrance);
        
        caveRoomNorth.setExit("south", caveEntrance);
        
        outsideOtherDimension.setExit("west", ruinsOtherDimension);
        outsideOtherDimension.setExit("east", oldShedOtherDimension);
        outsideOtherDimension.setExit("north", churchMainHall);
        outsideOtherDimension.lockExit("north", "brasskey");
        
        ruinsOtherDimension.setExit("east", outsideOtherDimension);
        ruinsOtherDimension.setExit("down", tunnelBeginning);
        ruinsOtherDimension.setExit("teleporter", teleporterRoom);
        
        oldShedOtherDimension.setExit("west", outsideOtherDimension);
        
        tunnelBeginning.setExit("north", tunnelEnding);
        tunnelBeginning.setExit("up", tunnelEnding);
        
        tunnelEnding.setExit("south", tunnelBeginning);
        tunnelEnding.setExit("up", churchWestRoom);
        
        churchWestRoom.setExit("east", churchMainHall);
        churchWestRoom.setExit("down", tunnelEnding);
        
        churchEastRoom.setExit("west", churchMainHall);
        churchEastRoom.setExit("down", basement);
        
        churchMainHall.setExit("north", churchAltarRoom);
        churchMainHall.setExit("east", churchEastRoom);
        churchMainHall.setExit("west", churchMainHall);
        churchMainHall.setExit("south", outsideOtherDimension);
        churchMainHall.lockExit("south", "oldbrasskey");
        
        basement.setExit("up", churchEastRoom);
        
        churchAltarRoom.setExit("up", towerLevel1);
        churchAltarRoom.setExit("south", churchMainHall);
        churchAltarRoom.lockExit("up", "rope");
        
        towerLevel1.setExit("up", towerLevel2);
        towerLevel1.setExit("down", churchAltarRoom);
        
        towerLevel2.setExit("west", teleporterRoom);
        towerLevel2.setExit("down", towerLevel1);
        
        //put all rooms in the rooms array
        rooms.put("caveEntrance", caveEntrance);
        rooms.put("caveRoomEast", caveRoomEast);
        rooms.put("caveRoomWest", caveRoomWest);
        rooms.put("caveRoomNorth", caveRoomNorth);
        rooms.put("outsideOtherDimension", outsideOtherDimension);
        rooms.put("oldShedOtherDimension", oldShedOtherDimension);
        rooms.put("ruinsOtherDimension", ruinsOtherDimension);
        rooms.put("tunnelBeginning", tunnelBeginning);
        rooms.put("tunnelEnding", tunnelEnding);
        rooms.put("churchWestRoom", churchWestRoom);
        rooms.put("churchEastRoom", churchEastRoom);
        rooms.put("churchMainHall", churchMainHall);
        rooms.put("churchAltarRoom", churchAltarRoom);
        rooms.put("basement", basement);
        rooms.put("towerLevel1", towerLevel1);
        rooms.put("towerLevel2", towerLevel2);
        
        return caveEntrance;  // start game outside
    }
    
    /**
     * Prepare the item, and give him his initial room
     */
    private void prepareItems()
    {
        //create the items
        Weapon BFG = new Weapon("BFG9000", "a BIG FUCKING GUN", 100000, 9000);
        Weapon sword = new Weapon("sword", "a knight's sword !", 40, 30);
        Weapon bareFists = new Weapon("barefists", "No gun?! No problem!", 0, 2); 
        Weapon kitchenKnife = new Weapon("kitchenknife", "Comon ... ! chop chop !", 2, 5);
        Weapon fireAxe = new Weapon("fireaxe", "Comon ... ! chop chop !", 10, 30);
        Weapon woadenStick = new Weapon("woadenstick", "Seriously ... A FLIPPIN' STICK! Oo", 1, 1);
        QuestItem rope = new QuestItem("rope", "an old, thick rope, looks like it's capable of holding alot of weight, maybe it can replace a ladder", 10, 5);
        QuestItem rustyKey = new QuestItem("rustykey", "an old and very rusty key", 2, 5);
        QuestItem teleportStone = new QuestItem("shinystone", "a shining rock. shiney", 5, 5);
        QuestItem brassKey = new QuestItem("brasskey", "an old key made out of brass, I wonder in what keyhole it fits", 1, 3);
        Consumeable magicalCookie = new Consumeable("magicalcookie", "Looks like a shining cookie to me.., eating it would be a shame.", 1, 10);
        Beamer beamer = new Beamer("beamer", "A big-ass personal teleporter, AKA beamer. Scotty not included", 10, 0);
        
        //give items a room
        rooms.get("caveRoomEast").addItem(BFG);
        rooms.get("caveEntrance").addItem(beamer);
        rooms.get("basement").addItem(sword);
        rooms.get("oldShedOtherDimension").addItem(rope);
        rooms.get("caveRoomEast").addItem(rustyKey);
        rooms.get("caveRoomNorth").addItem(teleportStone);
        rooms.get("churchEastRoom").addItem(brassKey);
        rooms.get("caveRoomNorth").addItem(magicalCookie);
        rooms.get("oldShedOtherDimension").addItem(kitchenKnife);
        rooms.get("oldShedOtherDimension").addItem(fireAxe);
        rooms.get("churchMainHall").addItem(woadenStick);
        
        //give items to player/monster
        player1.addItem(bareFists);
    }
    
    /**
     * Prepare the monster, and give him his initial room
     */
    private void prepareMonsters()
    {
        //Set Boss
        Monster mutatedCow = new Monster("The Giant Mutated Cow, with an axe", 100, rooms.get("towerLevel2"), 20, "blargh.mp3", "eugh.mp3");
        rooms.get("towerLevel2").setMonster(mutatedCow);
        
        //Set Monsters
        Monster gorn = new Monster("Gorn", 20, rooms.get("basement"), 10, "blargh.mp3", "eugh.mp3");
        rooms.get("basement").setMonster(gorn);
        
        Monster evilSquirrle = new Monster("The Evil Squirrle", 10, rooms.get("outsideOtherDimension"), 10, "blargh.mp3", "eugh.mp3");
        rooms.get("outsideOtherDimension").setMonster(evilSquirrle);
        
        Monster destructorMole = new Monster("The Destructor Mole", 10, rooms.get("outsideOtherDimension"), 10, "blargh.mp3", "eugh.mp3");
        rooms.get("tunnelEnding").setMonster(destructorMole);

    }
    
    /**
     * Prepare the first player.
     * @param startRoom The room the player starts in.
     */
    private void prepareCharacters(Room startRoom)
    {
        player1 = new Player("The dude", 120, 80, 100, startRoom);
    }
    
    /**
     *  Main play routine.  Loops until end of play.
     */
    public void play() 
    {            
        printWelcome();

        // Enter the main command loop.  Here we repeatedly read commands and
        // execute them until the game is over.

        while (!finished) {
            Command command = parser.getCommand();
            processCommand(command);
        }
        
        System.out.println(lastMessage);
    }

    /**
     * Print out the opening message for the player.
     */
    private void printWelcome()
    {
        String playerName = player1.getName();
        
        System.out.println("----------------------------------------------------------------------------------------------------------------------------------------------------------------------");
        System.out.println("Welcome to the World of Zuul!");
        System.out.println("----------------------------------------------------------------------------------------------------------------------------------------------------------------------");
        System.out.println("World of Zuul is a new, incredibly exciting adventure game about a man named " + "\"" +  playerName + "\"" + ".");
        System.out.println(playerName + " was just celebrating Thanksgiving with his family in his lovely little house.");
        System.out.println("At dinner time, he walked towards the kitchen to get him some of this 'damn delicious' turkey that his wife made him,");
        System.out.println("Suddenly he heard a loud scream coming out of the dining room. " + playerName + " picked up the turkey, put it in his mouth,");
        System.out.println("then ran as fast as he could back to the dining room. Once he arrived, he saw this note laying on the table. He read the note.");
        System.out.println(playerName + " knew that the note he just read was not a normal note, when said \"I GOT YOUR FAMILIY. COME GET THEM, THEY ARE IN THE DARK CAVE IN THE FOREST\".");
        System.out.println("He knew that it was..... and REALLY was..... ADVENTURE TIME !");
        System.out.println("----------------------------------------------------------------------------------------------------------------------------------------------------------------------");
        System.out.println();
        System.out.println("| (• ◡•)| (❍ᴥ❍ʋ)");
        System.out.println();
        System.out.println("Type 'help' if you need help.");       
        LOOK();
        
    }

    /**
     * Given a command, process (that is: execute) the command.
     * @param command The command to be processed.
     */
    private void processCommand(Command command) 
    {
        Method method;
        CommandWord commandWord = command.getCommandWord();
        
        boolean wantToQuit = false;
        commandCounter++;
        
        //use the Method class to execute a method based on the commandword.
        //so, if the commandword is go, executes the method named GO.
        //commands are automagically uppercased
        try{
            //get the name of the class whose method need to be executed (this one)
            //we're using getName() instead of a simple String, in case this class ever gets renamed.
            String nameOfThisClass = this.getClass().getName();
            Class<?> c = Class.forName(nameOfThisClass);
            
            //the name of the command we want, uppercased. these must equal a command name here.
            String commandName = commandWord.toString().toUpperCase();
            
            /*some functions require the command object as argument, (Command command)
             * while some other's do not. the reflective method below needs to know what arguments
               it will have to give, and what that value is.
               The TRY below will attempt to find and execute the code WITHOUT the argument.
               If it fails (eg: throws a NoSuchMethodException) we'll try again with the 
               argument.
               
               This beats simply adding an useless argument everywhere, since that wouldn't make it possible 
               for any method to call those methods, unless they happen to have that command
               as well. (for example, printWelcome() uses LOOK() now, but wouldn't be able to
               use it if it needed a Command as argument.
               */
            try{
                //find this function without argument, then execute it.
                method = c.getDeclaredMethod(commandName);
                method.invoke (this);
                //if it does not exist...
            }catch(NoSuchMethodException e){
                //try to find it again, but now with argument
                method = c.getDeclaredMethod (commandName, Command.class);
                method.invoke (this, command);
            }
            
        }
        catch(NoSuchMethodException e)
        {
            //if it's still not found, then the function doesn't exist (UNKNOWN)
            System.out.println("I don't know what you mean...");
        }
        catch(ClassNotFoundException e)
        {
            //thrown when the given class name doesn't exist. since it always uses it's own name,
            //it should never be able to get here.
            System.out.println("Internal error," + e.getMessage());
        }
        catch(IllegalAccessException e)
        {
            //thrown when the given method exists, but can't be accessed.
            //since this function is always self-reflective (and can use private methods)
            //it should never be able to get here.
            System.out.println("whoops! IllegalAccessException: " + e.getMessage());
        }
        catch(InvocationTargetException e)
        {
            //if the reflected (eg. underlying) method throws an exception, it will be caught within this exception
            //use e.getCause() to get the actual exception.
            System.out.println("whoops! InvocationTargetException: " + e.getCause());
        }
    }

    // implementations of user commands:

    /**
     * Print out some help information.
     * Here we print a standard message and a list of all 
     * available command words.
     */
    private void HELP() 
    {
        System.out.println();
        System.out.println("You are lost. You are alone. ");
        System.out.println("You are regretting that you forgot your shotgun.");
        System.out.println();
        System.out.println("Your command words are:");
        parser.showCommands();
    }

    /**
     * Unlock an exit using an item
     * @param command The command that was issued, as Command object
     */
    private void UNLOCK(Command command)
    {
        //check if the 2 necessary words (what to unlock and what to unlock it with) are present
        if(!command.hasSecondWord())
        {
            System.out.println();
            System.out.println("unlock what?");
            System.out.println();
            return;
        }
        
        if(!command.hasThirdWord())
        {
            System.out.println();
            System.out.println("unlock using what?");
            System.out.println();
            return;
        }
        
        String directionToUnlock = command.getSecondWord();
        String itemToUse = command.getThirdWord();
        
        //if the player has the item
        if(player1.hasItem(itemToUse))
        {
            //attempt to unlock an exit.
            boolean unlocked = player1.unlock(directionToUnlock, itemToUse);
            
            if(unlocked)
            {
                //if we succeeded, remove the used item
                player1.removeItem(itemToUse);
                System.out.println();
                System.out.println("exit unlocked!");
                System.out.println();
            }
            else
            {
                //if it doesn't, print a vague message.
                System.out.println();
                System.out.println("hmm, that won't do it...");
                System.out.println();
            }
        }
        else
        {
            //if the player tries to open an exit with an item he doesn't have
            System.out.println();
            System.out.println("You don't have that!");
            System.out.println();
        }
    }

    /**
     * Take an item from a room
     * @param command The command that was issued, as Command object
     */
    private void TAKE(Command command)
    {
        if(!command.hasSecondWord())
        {
            System.out.println();
            System.out.println("take what?");
            System.out.println();
            return;
        }
        
        String itemToGet = command.getSecondWord();
        
        player1.getItemFromRoom(itemToGet);
    }
    
    /** 
     * Try to go in one direction. 
     * If the exit is locked or doesn't exist, print an error.
     * @param command The command that was issued, as Command object
     */
    private void GO(Command command) 
    {
        if(!command.hasSecondWord()) {
            // if there is no second word, we don't know where to go...
            System.out.println();
            System.out.println("Go where?");
            System.out.println();
            return;
        }
        String direction = command.getSecondWord();
  
        //easter egg!
        if(direction.equals("nuts"))
        {
            System.out.println();
            System.out.println("you went nuts, taking the nearest object and smashing your head on it.\nGood thing those helmets have cushions.");
            System.out.println();
            return;
        }

        // Try to leave current room.
        // check if the door is locked
        String doorLock = player1.isExitLocked(direction);
        //if the room exists, and is not locked...
        if(doorLock == null)
        {
            if(player1.getCurrentRoom().getMonster() == null)
            {
                Room nextRoom = player1.getExit(direction);
            
                //If the next room is an instance of the teleporter room (itself a child class of Room)
                //get a random room and set that one instead.
                //the old rooms list is still being updated, since a next teleport could move you somewhere else
                oldRooms.addLast(player1.getCurrentRoom());
                if(nextRoom instanceof TeleportRoom)
                {
                    System.out.println();
                    System.out.println("it's a teleporter!");
                    System.out.println();
                    nextRoom = nextRoom.getRandomRoom();
                }
                
                //if next room is a traproom
                //check if there is a trapdoor
                //if trapdoor is true, than nextRoom stays the same
                //if trapdoor is false, than set next room to basement
                if(nextRoom instanceof TrapRoom){
                    if(!nextRoom.getFallen()){
                        nextRoom.setFallen(true);
                        nextRoom = nextRoom.getRoomToFallTo();
                    }
                }
                //move the player to the next room.
                player1.setCurrentRoom(nextRoom);
                LOOK();
            }
            else
            {
                System.out.println("You cannot leave the room while a monster is in it.");

            }
        }
        //if the room does not exist...
        else if(doorLock.equals("doesNotExist"))
        {
            System.out.println();            
            System.out.println("There is no door!");
            System.out.println();
        }
        //if the room exists, but is locked
        else
        {
            System.out.println();
            System.out.println("this door is locked! it needs 1 " + doorLock + " use the unlock command to unlock a door.");
            System.out.println();
        }
        
    }
    
    /**
     * Eat something
     * @param command The command that was issued, as Command object
     */
    private void EAT(Command command)
    {
        if(!command.hasSecondWord())
        {
            System.out.println();        
            System.out.println("eat what?");
            System.out.println();         
            return;
        }
        
        String itemToEat = command.getSecondWord();
        
        System.out.println(player1.eatItem(itemToEat));
        
    }
    
    /**
     * Go back to the previous room.
     * @param command The command that was issued, as Command object
     */
    private void BACK()
    {
        if(!oldRooms.isEmpty())
        {
            //if the player has ever moved room, move back to the previous one.
            player1.setCurrentRoom(oldRooms.pollLast());
            LOOK();
        }
        else
        {
            System.out.println();
            System.out.println("You haven't gone anywhere!");
            System.out.println();            
        }
    }
    
    /**
     * look at the room again.
     */
    private void LOOK()
    {
        System.out.println(player1.getCurrentRoom().getLongDescription());
    }
    
    /**
     * give a list of all items the player has.
     */
    private void ITEMS()
    {
        System.out.println(player1.getItemOverview());
    }
    
    /**
     * load or unload the beamer, if the player has one.
     * Scotty not included
     */
    private void TELEPORT(Command command)
    {
        if(!command.hasSecondWord())
        {
            System.out.println();
            System.out.println("Teleport requires a load or use keyword");
            System.out.println();            
        }
        
        //check if a player has a beamer
        if(!player1.hasItem("beamer"))
        {
            System.out.println();
            System.out.println("you don't have a beamer with you!");
            System.out.println();
            return;
        }
        
        Beamer beamer = (Beamer) player1.getItem("beamer");
        String function = command.getSecondWord().toLowerCase();
        
        System.out.println(function);
        
        if(function.equals("load"))
        {
            //get the current room
            Room currentRoom = player1.getCurrentRoom();
            
            //put it in the beamer
            beamer.load(currentRoom);
            
            //print a message
            System.out.println();
            System.out.println("Loaded the beamer!");
            System.out.println();
        }
        else if(function.equals("use"))
        {
            //if there is a loaded room...
            if(beamer.isLoaded())
            {
                player1.setCurrentRoom(beamer.getLoadedRoom());
                LOOK();
            }
            else
            {
                System.out.println();
                System.out.println("The beamer hasn't been loaded yet.");
                System.out.println();
            }
        }
        else{
            System.out.println();
            System.out.println("invalid command: use or load required as a second word.");
            System.out.println();
        }
    }

    /** 
     * "Quit" was entered. Check the rest of the command to see
     * whether we really quit the game.
     * @param command The command that was issued, as Command object
     */
    private void QUIT(Command command) 
    {
        if(command.hasSecondWord()) {
            System.out.println();
            System.out.println("Quit what?");
            System.out.println();
        }
        else {
            System.out.println();
            System.out.println("You have given " + commandCounter + " commands");
            System.out.println();
            this.finished = true;  // signal that we want to quit
        }
    }
    
    /**
     * Attack a monster.
     * @param command The command that was issues, as Command object
     */
    private void ATTACK(Command command)
    {
        if(command.hasSecondWord()) {
            System.out.println();
            System.out.println("Attack what?");
            System.out.println();
        }
        else {
            System.out.println();    
            System.out.println("attacking...");
            String attackResult = player1.attack();
            
            if(attackResult == "defeat")
            {
                //player defeated
                System.out.println();
                lastMessage = "You have lost!  Good bye.";
                System.out.println();
                finished = true;
            }
            else if(attackResult == "victory boss")
            {
                System.out.println("The BOSS has been slayned !");  
                finished = true;

            }else if(attackResult == "victory monster")
            {
                System.out.println("The monster has been slayned !");  
            }
            else if(attackResult == "combat")
            {
                System.out.println("The fight continues...");
                System.out.println();    
            }
        }
    }
}